package jsonrpc

import (
	"fmt"
	"reflect"
	"unicode"
	"unicode/utf8"
)

type methodType struct {
	method    reflect.Method
	ArgType   reflect.Type
	ReplyType reflect.Type
	AuthType  reflect.Type
	numType   int
	// sync.Mutex // protects counters
	// numCalls   uint
}

type service struct {
	name   string
	rcvr   reflect.Value
	typ    reflect.Type
	method map[string]*methodType
}

func (s *service) call(mtype *methodType, argv, replyv, authv reflect.Value) error {
	function := mtype.method.Func
	fmt.Println("开始调用Call")
	returnValues := []reflect.Value{}
	if mtype.numType == 3 {
		returnValues = function.Call([]reflect.Value{s.rcvr, argv, replyv})
	} else {
		returnValues = function.Call([]reflect.Value{s.rcvr, argv, replyv, authv})
	}
	fmt.Println("完成调用Call")
	Log.Debug("[service.call] argv: ", argv, " replyv: ", replyv)
	if i := returnValues[0].Interface(); i != nil {
		return i.(error)
	}
	return nil
}

// type isExported
func isExported(name string) bool {
	rune, _ := utf8.DecodeRuneInString(name)
	return unicode.IsUpper(rune)
}

// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
	for t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	// PkgPath will be non-empty even for an exported type,
	// so we need to check the type name as well.
	return isExported(t.Name()) || t.PkgPath() == ""
}

func suitableMethod(method reflect.Method) *methodType {
	mtype := method.Type
	mname := method.Name

	// Method must be exported.
	if method.PkgPath != "" {
		return nil
	}
	// Method needs three ins: receiver, *args, *reply.
	fmt.Println("mtype.NumIn:", mtype.NumIn())
	numIn := mtype.NumIn()
	if mtype.NumIn() != 4 {
		if mtype.NumIn() !=3{
			Log.Error("rpc.Register: method %q has %d input parameters; needs exactly three\n", mname, mtype.NumIn())
			return nil
		}

	}
	// First arg need not be a pointer.
	argType := mtype.In(1)
	if !isExportedOrBuiltinType(argType) {
		Log.Error("rpc.Register: argument type of method %q is not exported: %q\n", mname, argType)
		return nil
	}
	// Second arg must be a pointer.
	replyType := mtype.In(2)
	if replyType.Kind() != reflect.Ptr {
		Log.Error("rpc.Register: reply type of method %q is not a pointer: %q\n", mname, replyType)
		return nil
	}
	authType := *new(reflect.Type)
	if numIn == 4 {
		authType = mtype.In(3)
		if replyType.Kind() != reflect.Ptr {
			Log.Error("rpc.Register: reply type of method %q is not a pointer: %q\n", mname, replyType)
			return nil
		}
	}

	// Reply type must be exported.
	if !isExportedOrBuiltinType(replyType) {
		Log.Error("rpc.Register: reply type of method %q is not exported: %q\n", mname, replyType)
		return nil
	}
	// Method needs one out.
	if mtype.NumOut() != 1 {
		Log.Error("rpc.Register: method %q has %d output parameters; needs exactly one\n", mname, mtype.NumOut())
		return nil
	}
	// The return type of the method must be error.
	if returnType := mtype.Out(0); returnType != typeOfError {
		Log.Error("rpc.Register: return type of method %q is %q, must be error\n", mname, returnType)
		return nil
	}
	fmt.Println("server_map-suitableMethod-method.ArgType", argType)
	fmt.Println("server_map-suitableMethod-ReplyType:", replyType)
	return &methodType{method: method, ArgType: argType, ReplyType: replyType, AuthType: authType, numType: numIn}
}

func suitableMethods(typ reflect.Type) map[string]*methodType {
	methods := make(map[string]*methodType)
	for m := 0; m < typ.NumMethod(); m++ {
		fmt.Println("m", m)
		method := typ.Method(m)
		fmt.Println("method:", method)
		if mt := suitableMethod(method); mt != nil {
			fmt.Println("server_map-suitableMethods-method.Name", method.Name)
			fmt.Println("server_map-suitableMethods-mt:", mt)
			methods[method.Name] = mt
		}
	}
	return methods
}
